package consumer

import (
	"encoding/json"
	"fmt"
	"math/rand"
	"cppcloud"
	"strings"
	"sync"
	"time"
	"tool"
)

// 服务列表管理器: 从serv获得服务提供者列表，并定时更新

type SvrListMgr struct {
	// 服务列表，每个regname对应多个svr
	svrList   map[string]interface{}
	rwMutex   sync.RWMutex
	refreshCh chan string

	cloudapp *cppcloud.CloudApp
}

type svrInfo_t struct {
	regname  string
	svrid    int
	host     string
	port     int // 不提供时会选择一些随机端口
	url      string
	scheme   string // 和protocol，任选一个指定协议
	protocol int    // 1 tcp, 2 udp, 3 http, 4 https
	weight   int    // default = 100
	desc     string
	prvdid   int

	hostp  string
	conKey string
}

func CreateSvrListMgr(capp *cppcloud.CloudApp, regList ...string) *SvrListMgr {
	ret := &SvrListMgr{svrList: make(map[string]interface{}), cloudapp: capp}
	for _, regName := range regList {
		if 0 != ret.reqRegPrvdListByName(regName) {
			return nil
		}
	}

	var thisHandle cppcloud.MessageHandle = ret
	capp.SetCMDHandler(cppcloud.CMD_SVRSEARCH_RSP, 0, &thisHandle, false)
	capp.AddNotifyCallBack("provider_down", ret.onProviderDown)
	capp.AddNotifyCallBack("shudown", ret.onAppShutdown)

	ret.refreshCh = make(chan string, 2)
	go ret.refreshRoutine()
	return ret
}

func makeSvrInfo_t(dict map[string]interface{}) (*svrInfo_t, error) {
	svrInfo := &svrInfo_t{}
	svrInfo.regname, _ = tool.JSONGetString(dict, "regname")
	svrInfo.url, _ = tool.JSONGetString(dict, "url")
	if "" == svrInfo.regname || "" == svrInfo.url {
		return nil, &RunError{"No url or regname"}
	}

	svrInfo.scheme, _ = tool.JSONGetString(dict, "scheme")
	svrInfo.desc, _ = tool.JSONGetString(dict, "desc")
	svrInfo.host, _ = tool.JSONGetString(dict, "host")
	svrInfo.port, _ = tool.JSONGetInt(dict, "port")
	svrInfo.svrid, _ = tool.JSONGetInt(dict, "svrid")
	svrInfo.prvdid, _ = tool.JSONGetInt(dict, "prvdid")
	svrInfo.weight, _ = tool.JSONGetInt(dict, "weight")
	svrInfo.protocol, _ = tool.JSONGetInt(dict, "protocol")
	if 0 == svrInfo.svrid || svrInfo.weight <= 0 || 0 == svrInfo.protocol {
		return nil, &RunError{"No svrid or protocol"}
	}

	addrStrs := strings.Split(svrInfo.url, "//")
	if len(addrStrs) != 2 {
		return nil, &RunError{"invalid URL " + svrInfo.url}
	}

	svrInfo.conKey = fmt.Sprintf("%s-%d-%d", svrInfo.regname, svrInfo.svrid, svrInfo.prvdid)
	svrInfo.hostp = addrStrs[1]
	return svrInfo, nil
}

// 通过服务发现接口查询得到提供者列表
func (svrmgr *SvrListMgr) reqRegPrvdListByName(regName string) int {
	reqMap := map[string]interface{}{
		"regname":    regName,
		"bookchange": 1,
	}

	rspMap, errno := svrmgr.cloudapp.RequestMap(cppcloud.CMD_SVRSEARCH_REQ, reqMap)
	if 0 != errno {
		fmt.Printf("SVRSEARCH| err=%d| regName=%s\n", errno, regName)
		return -1
	}

	code, ok := tool.JSONGetInt(rspMap, "code")
	if !ok || 0 != code {
		fmt.Println("SVRSEARCH| msg=resp fail| regName="+regName, "rsp=", rspMap)
		return -2
	}

	return svrmgr.setPrvdData(regName, rspMap)
}

func (svrmgr *SvrListMgr) setPrvdData(regName string, rspMap map[string]interface{}) int {
	if "" == regName {
		if name, ok := tool.JSONGetString(rspMap, "data", 0, "regname"); ok && "" != name {
			regName = name
		} else {
			fmt.Println("SVRSEARCH| msg=unknow regname| rsp =", rspMap)
			return -3
		}
	}

	regList, ok := tool.JSONGetValue(rspMap, "data").([]interface{})
	if !ok || nil == regList {
		fmt.Println("SVRSEARCH| msg=no data rsp| rsp =", rspMap)
		return -4
	}

	regDict := calcSvrItems(regList)
	svrmgr.rwMutex.Lock()
	defer svrmgr.rwMutex.Unlock()
	svrmgr.svrList[regName] = regDict

	return 0
}

func calcSvrItems(regList []interface{}) map[string]interface{} {
	weightSum := 0
	weightEach := []int{}
	svrInfoArray := []interface{}{} // []*svrInfo_t{}
	for _, item := range regList {
		if svritem, ok := item.(map[string]interface{}); ok {
			svrinfo, err := makeSvrInfo_t(svritem)
			if nil != err {
				fmt.Println("CALCSVRITEM| msg=invalid svritem|", svritem, ", err=", err.Error())
				continue
			}

			weightSum += svrinfo.weight
			weightEach = append(weightEach, svrinfo.weight)
			svrInfoArray = append(svrInfoArray, svrinfo)
		}
	}

	if weightSum > 0 && len(weightEach) > 0 {
		return map[string]interface{}{
			"weightSum":   weightSum,
			"weightEach":  weightEach,
			"reglist":     regList,
			"svrInfoList": svrInfoArray,
		}
	}
	return nil
}

func (svrmgr *SvrListMgr) ProcessMessage(errCode int, cmdid, seqid uint16, bodystr string) *cppcloud.MessageData {
	rspMap := make(map[string]interface{})
	json.Unmarshal([]byte(bodystr), &rspMap)
	svrmgr.setPrvdData("", rspMap)
	return nil
}

func (svrmgr *SvrListMgr) onAppShutdown(msg map[string]interface{}) (code int, result interface{}) {
	svrmgr.refreshCh <- "shutdown"
	return 0, nil
}

func (svrmgr *SvrListMgr) onProviderDown(msg map[string]interface{}) (code int, result interface{}) {
	regname, ok1 := tool.JSONGetString(msg, "regname")
	svrid, ok2 := tool.JSONGetInt(msg, "svrid")
	prvdid, _ := tool.JSONGetInt(msg, "prvdid")

	if !ok1 || !ok2 {
		fmt.Println("PRVDERDOWN| msg=miss param| ", msg)
		return 0, nil
	}

	regValue := tool.JSONGetValue(svrmgr.svrList, regname)
	if nil == regValue {
		return 0, nil
	}
	regDict := regValue.(map[string]interface{})

	activeRegList := []interface{}{}
	reglist := tool.JSONGetValue(regDict, "reglist").([]interface{})
	for _, svrItem := range reglist {
		itmSvrid, ok1 := tool.JSONGetInt(svrItem, "svrid")
		itmPvrdid, _ := tool.JSONGetInt(svrItem, "prvdid")
		if ok1 && itmSvrid == svrid {
			if 0 == prvdid || prvdid == itmPvrdid {
				// 下线的项，要移除
				continue
			}
		}

		activeRegList = append(activeRegList, svrItem)
	}

	regDict = calcSvrItems(activeRegList)
	svrmgr.rwMutex.Lock()
	defer svrmgr.rwMutex.Unlock()
	svrmgr.svrList[regname] = regDict
	if nil == regDict {
		svrmgr.refreshCh <- regname
	}

	return 0, nil
}

//
func (svrmgr *SvrListMgr) CheckConsumer(regName string) *svrInfo_t {
	if _, ok := svrmgr.svrList[regName]; ok {
		svrmgr.refreshCh <- regName
	} else { // 如果是首次消费此服务，则进行一次‘服务发现’请求，之后要等定时进行
		svrmgr.svrList[regName] = nil
		result := "fail"
		defer fmt.Println("CHECKCOMSUMER| msg=search service| regname=" + regName + "| " + result)
		if 0 == svrmgr.reqRegPrvdListByName(regName) {
			result = "ok"
			return svrmgr.GetSvrItem(regName)
		}
	}
	return nil
}

// GetSvrItem 根据权重随机出一个合适的服务提供者
func (svrmgr *SvrListMgr) GetSvrItem(regName string) (ret *svrInfo_t) {
	svrmgr.rwMutex.RLock()
	weightSum, _ := tool.JSONGetInt(svrmgr.svrList, regName, "weightSum")
	weightEachObj := tool.JSONGetValue(svrmgr.svrList, regName, "weightEach")
	svrmgr.rwMutex.RUnlock()

	if weightSum <= 0 || nil == weightEachObj {
		fmt.Println("SVRITEM| msg=get svritem fail| regname" + regName)
		return nil
	}

	weightEach, ok := weightEachObj.([]int)
	if !ok {
		fmt.Println("SVRITEM| msg=no weightEach Error| regname="+regName, weightEachObj)
		return nil
	}

	randNum := rand.Intn(weightSum)
	selIndex := 0
	tmpSum := 0
	for i, weight0 := range weightEach {
		tmpSum += weight0
		if randNum < tmpSum {
			selIndex = i
			break
		}
	}

	svrinfoObj := tool.JSONGetValue(svrmgr.svrList, regName, "svrInfoList", selIndex)
	if ret, ok = svrinfoObj.(*svrInfo_t); !ok {
		fmt.Printf("SVRITEM| msg=get svrInfoList Error| regname=%s| idx=%d\n", regName, selIndex)
	}
	return ret
}

// 定时更新服务信息协程
func (svrmgr *SvrListMgr) refreshRoutine() {
	const nomalInterValSec = 8 * 60 // 正常服务刷新间隔（秒）=8min
	const failInterValSec = 2 * 69  // 异常服务刷新间隔
	type refInfo struct {
		mtime  int64
		result bool
	}

	timer := time.NewTimer(time.Second * nomalInterValSec)
	refreshMap := map[string]*refInfo{}
	hasFail := false

	for {
		select {
		case notify := <-svrmgr.refreshCh:
			if "shutdown" == notify {
				return
			}

			if infFail, ok := refreshMap[notify]; ok {
				infFail.result = false
				if hasFail {
					continue
				}
			} else { // 新的服务
				refreshMap[notify] = &refInfo{0, false}
			}

		case <-timer.C:
		}

		now := time.Now().Unix()
		hasFail = false
		for regName, _ := range svrmgr.svrList {
			if info, ok := refreshMap[regName]; ok {
				interval := nomalInterValSec
				if !info.result {
					interval = failInterValSec
				}
				if now < info.mtime+int64(interval) {
					if !hasFail && !info.result {
						hasFail = true
					}
					continue // 刷新时间未到
				}
			}

			result := svrmgr.reqRegPrvdListByName(regName)
			weightSum, okWeight := tool.JSONGetInt(svrmgr.svrList, regName, "weightSum")
			fmt.Printf("REFRESHSVR| msg=%s result %d| weight=%d\n", regName, result, weightSum)

			reqResult := (0 == result && okWeight && weightSum > 0)
			refreshMap[regName] = &refInfo{now, reqResult}
			if !hasFail && !reqResult {
				hasFail = true
			}
		}

		if hasFail {
			timer.Reset(time.Second * nomalInterValSec)
		} else {
			timer.Reset(time.Second * failInterValSec)
		}
	}
}
